module material.bitmaptexture;

import canvas.abstractcanvas;
import material.mipmap;
import material.texture;
import utils.color;
import utils.vector2;
import render.render;
import std.math;
import cmath = std.c.math;

public enum TextureFilter {
    NONE,
    MIPMAP,
    RIPMAP,
    SAT,
}

T mapCoords(T)(Vector2f texCoords, Vector2i size, Vector2i p, Vector2i offset) {
    T res;
    /*res.x = cast(T.Type)(texCoords.x * size.x)+p.x+offset.x;
    res.y = cast(T.Type)((1.0f+texCoords.y) * size.y)+p.y+offset.y;*/
    float u = cmath.fmodf(texCoords.x, 1.0f);
    float v = cmath.fmodf((1.0f+texCoords.y), 1.0f);
    //can be between -1.0f to +1.0f
    if (u < 0)
        u += 1.0f;
    if (v < 0)
        v += 1.0f;
    //(try to) do tiling
    res.x = cast(T.Type)((u * size.x)+p.x+offset.x);
    res.y = cast(T.Type)((v * size.y)+p.y+offset.y);
    //erm... just to be on the safe side
    if (res.x<p.x)
        res.x = p.x;
    if (res.x>p.x+size.x-1)
        res.x = p.x+size.x-1;
    if (res.y<p.y)
        res.y = p.y;
    if (res.y>p.y+size.y-1)
        res.y = p.y+size.y-1;
    return res;
}

public class BitmapTexture : Texture {
    private TextureFilter mFilter;
    private AbstractCanvas mTexture;
    private AbstractCanvas mMipMap;
    private AbstractCanvas mRipMap;
    private AbstractCanvas mSAT;

    public void texture(AbstractCanvas tex) {
        mTexture = tex;
        regenerateFilter();
    }

    private void regenerateFilter() {
        mMipMap = mRipMap = mSAT = null;
        switch (mFilter) {
            case TextureFilter.MIPMAP:
                mMipMap = generateMipMap(mTexture);
                break;
            case TextureFilter.RIPMAP:
                mRipMap = generateRipMap(mTexture);
                break;
            case TextureFilter.SAT:
                mSAT = generateSAT(mTexture);
                break;
            case TextureFilter.NONE:
                /*fallthrough*/
            default:
                //nothing
        }
    }

    public void filter(TextureFilter filt) {
        mFilter = filt;
        regenerateFilter();
        std.stdio.writefln("Filter updated");
    }

    public this(AbstractCanvas tex) {
        texture = tex;
    }
    public this() {
    }

    private AbstractCanvas selectMipMapSource(inout RenderContext context,
        in uint level, out Vector2i out_tex_p, out Vector2i out_tex_size)
    {
        assert(mMipMap);

        int size;
        mipmapRect(level, mMipMap.height, out_tex_p, size);
        out_tex_size = Vector2i(size, size);
        return mMipMap;
    }

    private AbstractCanvas selectRipMapSource(inout RenderContext context,
        out Vector2i out_tex_p, out Vector2i out_tex_size)
    {
        assert(mRipMap);

        Vector2f dt = context.rinters.deltaTx + context.rinters.deltaTy;
        float lod_x = log2(mTexture.width*fabs(dt.x));
        float lod_y = log2(mTexture.height*fabs(dt.y));
        //std.stdio.writefln("%s %s",lod_x,lod_y);

        uint level_x = cast(uint)lod_x;
        uint level_y = cast(uint)lod_y;

        ripmapRect(level_x, level_y, mTexture.width, out_tex_p, out_tex_size);
        return mRipMap;
    }

    public RGBColor getColor(inout RenderContext context, Vector2i offset) {
        RGBColor col;

        if (!mTexture) {
            //f*cking compiler bug: return mColor; doesntwork
            col = super.getColor(context, offset);
            return col;
        }

        float lod = log2(fmax(mTexture.width*context.rinters.deltaTx.length,
            mTexture.height*context.rinters.deltaTy.length));


        if (lod>=0) {
            switch (mFilter) {
                case TextureFilter.MIPMAP:
                    //select mipmap level... using ray differentials :-))))
                    lod = fmax(0,lod);

                    //Interpolate between 2 mip levels, weighted with fractional
                    //part of lod

                    Vector2i p1, p2, s1, s2;
                    auto tex1 = selectMipMapSource(context, cast(uint)lod, p1, s1);
                    auto tex2 = selectMipMapSource(context, cast(uint)lod + 1, p2, s2);

                    Vector2i pixc1 = mapCoords!(Vector2i)(context.rinters.texcoords,s1,p1,offset);
                    Vector2i pixc2 = mapCoords!(Vector2i)(context.rinters.texcoords,s2,p2,offset);
                    RGBColor col1, col2;
                    tex1.getPixelColor(pixc1, col1);
                    tex2.getPixelColor(pixc2, col2);
                    float d = lod - cast(uint)lod;
                    col = (1.0 - d)*col1 + d*col2;
                    break;
                case TextureFilter.RIPMAP:
                    Vector2i p, s;
                    auto tex = selectRipMapSource(context, p, s);

                    Vector2i pixc = mapCoords!(Vector2i)(context.rinters.texcoords,s,p,offset);
                    tex.getPixelColor(pixc, col);
                    break;
                case TextureFilter.SAT:
                    Vector2i p0 = mapCoords!(Vector2i)(context.rinters.texcoords,mSAT.size,Vector2i(0,0),offset);
                    Vector2f t = context.rinters.texcoords+context.rinters.deltaTx+context.rinters.deltaTy;
                    Vector2i p1 = mapCoords!(Vector2i)(t,mSAT.size,Vector2i(0,0),offset);
                    //std.stdio.writefln("%s",p1);
                    col = getSATValue(mSAT,p0,p1);
                    break;
                case TextureFilter.NONE:
                default:
                    Vector2i pixc = mapCoords!(Vector2i)(context.rinters.texcoords,mTexture.size,Vector2i(0,0),offset);
                    mTexture.getPixelColor(pixc, col);
                    //nothing
            }
        } else {
            Vector2f pf = mapCoords!(Vector2f)(context.rinters.texcoords,mTexture.size,Vector2i(0,0),offset);
            pf = pf - Vector2f(0.5f,0.5f);
            Vector2i pi = Vector2i(cast(int)pf.x, cast(int)pf.y);
            float xfrac = pf.x-pi.x;
            float yfrac = pf.y-pi.y;
            RGBColor col0 = mTexture.getPixelColor(Vector2i(pi.x,pi.y));
            RGBColor col1 = mTexture.getPixelColor(Vector2i(pi.x+1,pi.y));
            RGBColor col2 = mTexture.getPixelColor(Vector2i(pi.x,pi.y+1));
            RGBColor col3 = mTexture.getPixelColor(Vector2i(pi.x+1,pi.y+1));
            col = (col0*(1-xfrac)+col1*xfrac)*(1-yfrac)+(col2*(1-xfrac)+col3*xfrac)*yfrac;
        }

        return col;
    }
}
